﻿using System;
using System.Collections.Generic;

using System.Text;
using System.Net.Sockets;
using System.Threading;

namespace Firefly.Net {
    public class GameConnection {

        public TelnetClient Client { get; private set; }
        public NetworkStream Stream { get; private set; }
        public event ConnectionStatusEventHandler ConnectionStatusChangeEvent;
        public event DataReceivedHandler DataReceivedEvent;
        public delegate void ConnectionStatusEventHandler(GameConnection sender, ConnectionStatusEventArgs data);
        public delegate void DataReceivedHandler(GameConnection sender, string data);

        public GameConnection(string server, int port) {
            this.mServerAddress = server;
            this.mServerPort = port;
            this.Client = new TelnetClient();
            this.oStack = new Stack<string>();
            rThread = new Thread(new ThreadStart(ReadSocketLoop));
            wThread = new Thread(new ThreadStart(WriteSocketLoop));
        }

        public void Connect() {
            Thread connectThread = new Thread(new ThreadStart(delegate {
                try {
                    // Create a TCP Client to initiate the connection.
                    this.Client.Connect(mServerAddress, mServerPort);
                    DataReceivedEvent.Invoke(this, "Connecting to " + mServerAddress + "...\n");
                    // Wait for the connection...
                    while (this.Client.Connected == false) ;
                    // Connected!
                    this.Stream = this.Client.GetStream();
                    // Send connect event.
                    ConnectionStatusChangeEvent.Invoke(this, new ConnectionStatusEventArgs() {
                        Output = "Successfully Connected - IPADDRESS (HOSTNAME) - Timestamp\n",
                        Connected = true
                    });
                    // Start read/write threads.
                    rThread.Start();
                    wThread.Start();
                }
                catch (Exception e) {
                    ConnectionStatusChangeEvent.Invoke(this, new ConnectionStatusEventArgs() {
                        Output = e.Message,
                        Connected = false
                    });
                }
            }));
            connectThread.Start();
        }

        public void Disconnect() {
            try {
                Stream.Close();
                Client.Close();
                Stream.WriteByte((byte)0x00);
            } catch (Exception e) {
                OnDisconnect();
            }
        }

        public bool ConnectionStatus() {
            try {
                Stream.Write(new byte[1], 0, 1);
            } catch (Exception e) {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Writes a new message to the stack to be written on the next write buffer.
        /// </summary>
        /// <param name="m"></param>
        public void Write(string m) {
            oStack.Push(m);
        }

        protected void ReadSocketLoop() {
            while (this.Client.Connected) {
                if(this.Stream.DataAvailable) {
                    byte bytePeek = (byte)Stream.ReadByte();
                    if ((byte)bytePeek != (byte)NegotiationCode.IAC) { // Not a telnet code?
                        Console.WriteLine(String.Format("non-intercepted byte: {0:G} (" + (int)bytePeek + ") or " + (char)bytePeek, (AllCodes)bytePeek));
                        byte[] bufferData = new byte[mBufferSize - 1];
                        Int32 bytes = Stream.Read(bufferData, 0, bufferData.Length);
                        byte[] data = new byte[mBufferSize];
                        data[0] = bytePeek;
                        for (int i = 1; i < mBufferSize; i++)
                            data[i] = bufferData[i - 1];
                        // Send the event out with new data.
                        DataReceivedEvent.Invoke(this, System.Text.Encoding.UTF8.GetString(data, 0, bytes + 1));
                    } else {
                        Client.Negotiate(bytePeek);
                    }
                }
                Thread.Sleep(20);
            }
        }

        protected void WriteSocketLoop() {
            while (this.Client.Connected) {
                if (oStack.Count > 0) {
                    string text = oStack.Pop();
                    byte[] data = System.Text.Encoding.UTF8.GetBytes(text);
                    // Send it off!
                    try {
                        Stream.Write(data, 0, data.Length);
                        if (text == "QUIT\n") // If the user typed quit, keep writing until something breaks!
                            while (this.Client.Connected)
                                Stream.WriteByte(0x00);
                    } catch (Exception e) {
                        // There was an error. The connection is actually ded.
                        Disconnect();
                    }
                }

                Thread.Sleep(20);
            }
        }

        protected void OnDisconnect() {
            if (ConnectionStatusChangeEvent != null)
                ConnectionStatusChangeEvent.Invoke(this, new ConnectionStatusEventArgs() {
                    Connected = false,
                    Output = (char)0x1B + "[1;33m**** DISCONNECTED ****" + (char)0x1B + "[0m"
                });
            rThread.Abort();
            wThread.Abort();
        }

        public string mServerAddress;
        public Int32 mServerPort;
        public int mBufferSize = 1024;
        // Threading
        protected Thread rThread;
        protected Thread wThread;
        // Output stack
        private Stack<string> oStack;
    }

    public class ConnectionStatusEventArgs : EventArgs {
        public string Output { get; set; }
        public bool Connected { get; set;}
    }
}
